﻿// Copyright (c) 2013 fancidev
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Security;

namespace Util.Media
{
    /// <summary>
    /// Encapsulates the pixel data of an image as a buffer of bytes.
    /// </summary>
    public abstract class PixelBuffer : IDisposable
    {
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
        }

        /// <summary>
        /// Gets the pixel format of the image buffer.
        /// </summary>
        public abstract PixelFormat PixelFormat { get; }

        /// <summary>
        /// Gets the total number of bytes in the buffer.
        /// </summary>
        public abstract int Length { get; }

        /// <summary>
        /// Reads the given number of bytes from the pixel buffer.
        /// </summary>
        public virtual void Read(int position, byte[] buffer, int offset, int count)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset");
            if (count < 0 || offset + count > buffer.Length)
                throw new ArgumentOutOfRangeException("count");
            if (position + count > this.Length)
                throw new ArgumentOutOfRangeException("count");

            ReadCore(position, buffer, offset, count);
        }

        protected virtual void ReadCore(int position, byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Writes the given bytes to the pixel buffer.
        /// </summary>
        public virtual void Write(int position, byte[] buffer, int offset, int count)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset");
            if (count < 0 || offset + count > buffer.Length)
                throw new ArgumentOutOfRangeException("count");
            if (position + count > this.Length)
                throw new ArgumentOutOfRangeException("count");

            WriteCore(position, buffer, offset, count);
        }

        protected virtual void WriteCore(int position, byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }
    }

    /// <summary>
    /// Encapsulates the pixel data of a GDI Bitmap as a buffer of bytes.
    /// 
    /// Each pixel is represented by a consecutive number of bits (usually in
    /// an integral number of bytes). The pixels are accessed scanline by 
    /// scanline from top to bottom, and in each scanline from left to right.
    /// No padding exists in a scanline.
    /// 
    /// Using this class is similar to accessing the underlying BitmapData
    /// directly except you don't need to worry about stride and orientation.
    /// </summary>
    public class BitmapPixelBuffer : PixelBuffer
    {
        private Bitmap bmp;
        private BitmapData bmpData;
        private int bytesPerScanline;

        // [SecurityCritical]
        public BitmapPixelBuffer(Bitmap bitmap, PixelFormat pixelFormat, ImageLockMode access)
        {
            if (bitmap == null)
                throw new ArgumentNullException("bitmap");
            if (access != ImageLockMode.ReadOnly &&
                access != ImageLockMode.WriteOnly &&
                access != ImageLockMode.ReadWrite)
                throw new ArgumentOutOfRangeException("access");
            int bpp = GetBitsPerPixel(pixelFormat);
            if (bpp < 8)
                throw new NotSupportedException("Pixel format not supported.");

            this.bmp = bitmap;
            this.bmpData = bitmap.LockBits(
                new Rectangle(0, 0, bmp.Width, bmp.Height),
                access,
                pixelFormat);
            this.bytesPerScanline = bmp.Width * (bpp / 8);
        }

        public BitmapPixelBuffer(Bitmap bitmap, PixelFormat pixelFormat)
            : this(bitmap, pixelFormat, ImageLockMode.ReadWrite) { }

        public BitmapPixelBuffer(Bitmap bitmap)
            : this(bitmap, bitmap.PixelFormat, ImageLockMode.ReadWrite) { }

        // [SecurityCritical]
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (bmp != null)
                {
                    bmp.UnlockBits(bmpData);
                    bmpData = null;
                    bmp = null;
                }
            }
        }

        public override PixelFormat PixelFormat
        {
            get { return bmp.PixelFormat; }
        }

        public override int Length
        {
            get { return bmp.Height * bytesPerScanline; }
        }

        protected override void ReadCore(int position, byte[] buffer, int offset, int count)
        {
            int y = position / bytesPerScanline;
            int x = position % bytesPerScanline;
            IntPtr ptr = bmpData.Scan0 + bmpData.Stride * y;

            // Read scanline by scanline.
            while (count > 0)
            {
                int bytesToRead = Math.Min(count, bytesPerScanline - x);
                Marshal.Copy(ptr + x, buffer, offset, bytesToRead);
                y++;
                x = 0;
                ptr += bmpData.Stride;
                offset += bytesToRead;
                count -= bytesToRead;
            }
        }

        protected override void WriteCore(int position, byte[] buffer, int offset, int count)
        {
            int y = position / bytesPerScanline;
            int x = position % bytesPerScanline;
            IntPtr ptr = bmpData.Scan0 + bmpData.Stride * y;

            // Write scanline by scanline.
            while (count > 0)
            {
                int bytesToWrite = Math.Min(count, bytesPerScanline - x);
                Marshal.Copy(buffer, offset, ptr + x, bytesToWrite);
                y++;
                x = 0;
                ptr += bmpData.Stride;
                offset += bytesToWrite;
                count -= bytesToWrite;
            }
        }

        private static int GetBitsPerPixel(PixelFormat format)
        {
            switch (format)
            {
                case PixelFormat.Format1bppIndexed:
                    return 1;
                case PixelFormat.Format4bppIndexed:
                    return 4;
                case PixelFormat.Format8bppIndexed:
                    return 8;
                case PixelFormat.Format16bppArgb1555:
                case PixelFormat.Format16bppGrayScale:
                case PixelFormat.Format16bppRgb555:
                case PixelFormat.Format16bppRgb565:
                    return 16;
                case PixelFormat.Format24bppRgb:
                    return 24;
                case PixelFormat.Format32bppArgb:
                case PixelFormat.Format32bppPArgb:
                case PixelFormat.Format32bppRgb:
                    return 32;
                case PixelFormat.Format48bppRgb:
                    return 48;
                case PixelFormat.Format64bppArgb:
                case PixelFormat.Format64bppPArgb:
                    return 64;
                default:
                    return 0;
            }
        }
    }

    /// <summary>
    /// Encapsulates a primitive array as a IPixelBuffer.
    /// </summary>
    public class ArrayPixelBuffer<T> : PixelBuffer
    {
        private T[] array;
        private PixelFormat pixelFormat;
        private int elemSize;
        private int bytesPerScanLine;
        private int stride;
        private int height;

        public ArrayPixelBuffer(T[] array, PixelFormat pixelFormat, 
                                int stride, int bytesPerScanLine)
        {
            this.array = array;
            this.pixelFormat = pixelFormat;
            this.elemSize = Marshal.SizeOf(typeof(T));
            this.bytesPerScanLine = bytesPerScanLine;
            this.stride = stride;
            this.height = array.Length * elemSize / stride;
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                array = null;
                elemSize = 0;
                pixelFormat = PixelFormat.Undefined;
            }
        }

        public override PixelFormat PixelFormat 
        {
            get { return pixelFormat; } 
        }

        /// <summary>
        /// Gets the total number of bytes in the buffer.
        /// </summary>
        public override int Length { get { return height * bytesPerScanLine; } }

        protected override void ReadCore(int position, byte[] buffer, int offset, int count)
        {
            if (bytesPerScanLine == stride)
            {
                Buffer.BlockCopy(array, position, buffer, offset, count);
                return;
            }

            int y = position / bytesPerScanLine;
            int x = position % bytesPerScanLine;

            // Read scanline by scanline.
            while (count > 0)
            {
                int bytesToRead = Math.Min(count, bytesPerScanLine - x);
                Buffer.BlockCopy(array, y * stride + x, buffer, offset, count);
                y++;
                x = 0;
                offset += bytesToRead;
                count -= bytesToRead;
            }
        }

        protected override void WriteCore(int position, byte[] buffer, int offset, int count)
        {
            if (bytesPerScanLine == stride)
            {
                Buffer.BlockCopy(buffer, offset, array, position, count);
                return;
            }

            int y = position / bytesPerScanLine;
            int x = position % bytesPerScanLine;

            // Read scanline by scanline.
            while (count > 0)
            {
                int bytesToWrite = Math.Min(count, bytesPerScanLine - x);
                Buffer.BlockCopy(buffer, offset, array, y * stride + x, count);
                y++;
                x = 0;
                offset += bytesToWrite;
                count -= bytesToWrite;
            }
        }
    }
}
